﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
namespace RStein.PosterousReader.Common.ViewModelsHelpers
{
  public class DefaultKnownTypesProvider : IKnownTypeProvider
  {
    public static readonly string TYPE_ID_FORMAT = "{0},{1}";
    public DefaultKnownTypesProvider()
    {

    }
    
    public IEnumerable<Type> GetKnownTypes()
    {             
       var types = (from type in getExecutingAssembly().GetExportedTypes().Union(getRefencedAssembliesTypes())                                
                   where type.GetCustomAttributes(typeof(DataContractAttribute), false).Any()
                   select type).ToList();
      
      
      var observablecollectionType = typeof(ObservableCollection<>);
            
      var collectionTypes = from type in types                            
                            where !type.IsGenericType || 
                                  (type.IsGenericTypeDefinition && type != observablecollectionType) ||
                                  (type.GetGenericTypeDefinition() != observablecollectionType)                                                        
                            select observablecollectionType.MakeGenericType(type);

      return types.Union(collectionTypes).Union(getTypesFromOtherProviders()).ToList();              
    }

    private static Assembly getExecutingAssembly()
    {
      var entryTypeString = Deployment.Current.EntryPointType;
      var assemblyString = Deployment.Current.EntryPointAssembly;
      var entryTypeFullId = String.Format(TYPE_ID_FORMAT, entryTypeString, assemblyString);

      Assembly executingAssembly = Type.GetType(entryTypeFullId).Assembly;
      return executingAssembly;
    }

    private IEnumerable<Type> getTypesFromOtherProviders()
    {
      
      var knownTypeProviderType = typeof(IKnownTypeProvider);
      var providers = selectProviderTypes(knownTypeProviderType);
      
      return providers.Cast<IKnownTypeProvider>().Aggregate(new List<Type>(),
                                                            (resultTypes, provider) => 
                                                              {
                                                                resultTypes.AddRange(provider.GetKnownTypes());
                                                                return resultTypes;
                                                              });
                                

    }

    
    private IEnumerable<Type> getRefencedAssembliesTypes()
    {
      var assemblyProviderType = typeof(IAssemblyTypesProvider);
      var providers = selectProviderTypes(assemblyProviderType).Cast<IAssemblyTypesProvider>();
    
      var types = (from provider in providers
                  from assembly in provider.GetAssemblies()
                  from type in assembly.GetExportedTypes()
                  select type).ToList();

      return types;      
      
    }

    private IEnumerable selectProviderTypes(Type providerType)
    {
      
      var providers = from type in getExecutingAssembly().GetExportedTypes()
                      where type != GetType() && !type.IsInterface &&
                            providerType.IsAssignableFrom(type)
                      select Activator.CreateInstance(type);

      return providers;
    }
  }
}
